Skip to content

test: null/dictionary bind columns, empty bound batch schema, int option coherence - #4

Closed
fornwall wants to merge 3 commits into
mainfrom
test/review-gap-tests
Closed

test: null/dictionary bind columns, empty bound batch schema, int option coherence#4
fornwall wants to merge 3 commits into
mainfrom
test/review-gap-tests

Conversation

@fornwall

Copy link
Copy Markdown
Owner

Adds four generic, driver-agnostic tests for gaps found while reviewing an ADBC driver (the adbc-spanner Rust driver) against this suite: each one encodes a contract the suite did not previously exercise and that a real driver got wrong. They are candidates for upstreaming to adbc-drivers/validation later.

Tests

1. TestStatement.test_parameter_null_typed (gated on statement_bind)

Binds a batch whose parameter column has Arrow type null and asserts NULL is bound per row (insert + read-back through the existing sample_table fixture / its query_override hook).

Reference: AdbcStatementGetParameterSchema doc comment in adbc.h: "If the type cannot be determined, the type of the corresponding field will be NA (NullType)." A client that builds its bind batch from the driver's own reported parameter schema therefore produces null-typed columns; pyarrow also infers null for an all-None parameter set (the DBAPI executemany shape). A driver advertising bind support that rejects the type its own GetParameterSchema hands out contradicts itself.

Expected on current adbc-spanner: FAIL (verified by emulator run): INVALID_ARGUMENT: cannot bind parameter "p2": unsupported Arrow type Null (REVIEW.md finding CONV-1 — bind.rs has no Null arm).

2. TestStatement.test_parameter_dictionary_encoded (gated on statement_bind)

Binds a dictionary-encoded string column (pa.array([...]).dictionary_encode() — what pandas categoricals produce over the C data interface) and asserts the decoded values (including a null) round-trip.

Reference: the Arrow columnar format, "Dictionary-encoded Layout": dictionary encoding is a representation of the same logical values, not a different logical type. No ADBC spec text explicitly requires drivers to accept every Arrow encoding, so this test asserts an ecosystem-consistency expectation stated here explicitly: a driver that binds plain string columns should accept (and may decode) the dictionary-encoded equivalent, since that is what common producers (pandas) emit.

Expected on current adbc-spanner: FAIL (verified by emulator run): the Arrow-to-Spanner bind path rejects Dictionary wholesale (REVIEW.md finding CONV-2).

3. TestStatement.test_parameter_execute_empty_bind (gated on statement_bind)

Implements the long-standing # TODO: also test with empty stream/empty batch in TestQuery.test_query (comment adjusted there): executes a parameterized SELECT with a zero-row bound batch and asserts the returned stream has zero rows but still carries the query's real result schema, identical to a non-empty execution of the same query.

Reference: no ADBC spec text spells out the zero-bound-rows case (AdbcStatementBind says only that bind is for "bulk inserts or prepared statements"). This test asserts a self-consistency invariant, stated here explicitly rather than cited: the result schema is a property of the query, not of the number of bound rows, so an empty executemany parameter set must not change the schema a client sees.

Expected on current adbc-spanner: FAIL (verified by emulator run): the empty-bind execution returns an empty (0-column) schema instead of the query's schema (REVIEW.md finding COR-9).

4. TestConnection.test_option_autocommit_int_coherence (gated on connection_transactions)

Sets adbc.connection.autocommit through SetOptionInt (a plain Python int via set_options); if the driver accepts that set, requires GetOptionInt on the same key to succeed and agree, and the string getter to agree too. Drivers that reject the integer-typed set are skipped, not failed. Restores autocommit afterwards (same pattern as test_transaction_toggle, which covers the bool-as-string round-trip; this test is specifically the int-typed accessor coherence).

Reference: AdbcConnectionGetOptionInt doc comment in adbc.h: "For standard options, drivers must always support getting the option value (if they support getting option values at all) via the type specified in the option. (For example, an option set via SetOptionDouble must be retrievable via GetOptionDouble.)" The skip-on-rejected-set keeps the test within that text: it only asserts the getter once the driver has itself accepted the int-typed set.

Expected on current adbc-spanner: FAIL (verified by emulator run): the int set succeeds but get_option_int errors (value "true" is not an integer) (REVIEW.md finding COR-4).

Verification

  • Framework checks: uv run pytest tests/ (210 passed, 1 skipped), uv run ty check (clean), pre-commit run --all-files (all hooks pass).
  • Driver evidence: all four tests executed against the adbc-spanner driver + Spanner emulator via its foundry harness at this branch's head — 4 failed for exactly the predicted driver bugs, 248 deselected (no collection breakage).
  • Regression check: a selection of pre-existing tests (test_prepare, test_transaction_toggle, test_nonascii_queries) still passes on the same driver with this branch installed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LGf8PVEe2tYkw8Q6Pd95tq

fornwall and others added 3 commits July 13, 2026 16:55
Two generic bind-parameter acceptance tests, gated on statement_bind:

- test_parameter_null_typed binds a batch whose parameter column has
  Arrow type null. AdbcStatementGetParameterSchema (adbc.h) reports NA
  (NullType) fields when a parameter type cannot be determined, so a
  batch built from the driver's own reported parameter schema (or
  pyarrow's inferred type for an all-None parameter set, as produced by
  DBAPI executemany) is null-typed; drivers must bind NULL per row.

- test_parameter_dictionary_encoded binds a dictionary-encoded string
  column (what pandas categoricals produce). Dictionary encoding is an
  encoding of the same logical values, not a different logical type
  (Arrow columnar format, Dictionary-encoded Layout), so a driver that
  binds strings should accept it, decoding if needed.

Both reuse the existing sample_table fixture (and its query_override
hook), inserting via bound parameters and reading the rows back.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGf8PVEe2tYkw8Q6Pd95tq
Signed-off-by: Fredrik Fornwall <fredrik@fornwall.net>
Implements the 'empty stream/empty batch' TODO in TestQuery.test_query
as a self-contained statement-level test, gated on statement_bind:
test_parameter_execute_empty_bind executes a parameterized SELECT with
a zero-row bound batch (the DBAPI executemany empty-parameter-set
shape) and asserts the returned stream has zero rows but still carries
the query's real result schema, matching a non-empty execution of the
same query. The result schema is a property of the query, not of the
number of bound rows; the ADBC spec does not spell the zero-row case
out explicitly, so this asserts the self-consistency invariant.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGf8PVEe2tYkw8Q6Pd95tq
Signed-off-by: Fredrik Fornwall <fredrik@fornwall.net>
…etOptionInt

adbc.h (AdbcConnectionGetOptionInt): 'For standard options, drivers
must always support getting the option value (if they support getting
option values at all) via the type specified in the option. (For
example, an option set via SetOptionDouble must be retrievable via
GetOptionDouble.)'

test_option_autocommit_int_coherence sets adbc.connection.autocommit
through SetOptionInt (a plain Python int via set_options) and, when the
driver accepts that set, requires GetOptionInt to succeed and agree
(and the string getter to agree as well). Drivers that reject the
integer-typed set are skipped, not failed. Gated on
connection_transactions since the test toggles autocommit off; restores
autocommit afterwards like test_transaction_toggle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGf8PVEe2tYkw8Q6Pd95tq
Signed-off-by: Fredrik Fornwall <fredrik@fornwall.net>
@fornwall

Copy link
Copy Markdown
Owner Author

Superseded — split into one PR per test as requested, each carrying the adbc-spanner emulator evidence plus new results against the released adbc-driver-sqlite 1.11.0 wheel:

The combined branch test/review-gap-tests is left in place for reference.

@fornwall fornwall closed this Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant